package com.github.niefy.modules.wx.port;


import org.apache.catalina.security.Constants;
import org.apache.catalina.session.TooManyActiveSessionsException;
import org.apache.tomcat.util.res.StringManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 基于内存的session manager.
 *
 * @author Daniel Qian
 */
public class StandardSessionManager implements WxSessionManager, InternalSessionManager {
    protected static final StringManager SM = StringManager.getManager(Constants.PACKAGE);
    /**
     * The descriptive name of this Manager implementation (for logging).
     */
    private static final String name = "SessionManagerImpl";
    protected final Logger log = LoggerFactory.getLogger(StandardSessionManager.class);
    private final Object maxActiveUpdateLock = new Object();
    /**
     * 后台清理线程是否已经开启
     */
    private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false);


    // -------------------------------------- InternalSessionManager
    /**
     * The set of currently active Sessions for this Manager, keyed by
     * session identifier.
     */
    protected Map<String, InternalSession> sessions = new ConcurrentHashMap<>();
    /**
     * The maximum number of active Sessions allowed, or -1 for no limit.
     */
    protected int maxActiveSessions = -1;

    /**
     * Number of session creations that failed due to maxActiveSessions.
     */
    protected int rejectedSessions = 0;

    /**
     * The default maximum inactive interval for Sessions created by
     * this Manager.
     */
    protected int maxInactiveInterval = 30 * 60;

    /**
     * Number of sessions created by this manager
     */
    protected long sessionCounter = 0;

    protected volatile int maxActive = 0;
    /**
     * Processing time during session expiration.
     */
    protected long processingTime = 0;
    /**
     * Frequency of the session expiration, and related manager operations.
     * Manager operations will be done once for the specified amount of
     * backgrondProcess calls (ie, the lower the amount, the most often the
     * checks will occur).
     */
    protected int processExpiresFrequency = 6;
    /**
     * background processor delay in seconds
     */
    protected int backgroundProcessorDelay = 10;
    /**
     * Iteration count for background processing.
     */
    private int count = 0;

    @Override
    public WxSession getSession(String sessionId) {
        return getSession(sessionId, true);
    }

    @Override
    public WxSession getSession(String sessionId, boolean create) {
        if (sessionId == null) {
            throw new IllegalStateException
                    (SM.getString("sessionManagerImpl.getSession.ise"));
        }

        InternalSession session = findSession(sessionId);
        if ((session != null) && !session.isValid()) {
            session = null;
        }
        if (session != null) {
            session.access();
            return session.getSession();
        }

        // Create a new session if requested and the response is not committed
        if (!create) {
            return null;
        }

        session = createSession(sessionId);

        if (session == null) {
            return null;
        }

        session.access();
        return session.getSession();
    }

    @Override
    public void remove(InternalSession session) {
        remove(session, false);
    }

    @Override
    public void remove(InternalSession session, boolean update) {
        if (session.getIdInternal() != null) {
            this.sessions.remove(session.getIdInternal());
        }
    }


    @Override
    public InternalSession findSession(String id) {
        if (id == null) {
            return null;
        }
        return this.sessions.get(id);
    }

    @Override
    public InternalSession createSession(String sessionId) {
        if (sessionId == null) {
            throw new IllegalStateException
                    (SM.getString("sessionManagerImpl.createSession.ise"));
        }

        if ((this.maxActiveSessions >= 0) &&
                (getActiveSessions() >= this.maxActiveSessions)) {
            this.rejectedSessions++;
            throw new TooManyActiveSessionsException(
                    SM.getString("sessionManagerImpl.createSession.tmase"),
                    this.maxActiveSessions);
        }

        // Recycle or create a Session instance
        InternalSession session = createEmptySession();

        // Initialize the properties of the new session and return it
        session.setValid(true);
        session.setCreationTime(System.currentTimeMillis());
        session.setMaxInactiveInterval(this.maxInactiveInterval);
        session.setId(sessionId);
        this.sessionCounter++;

        return session;
    }


    @Override
    public int getActiveSessions() {
        return this.sessions.size();
    }


    @Override
    public InternalSession createEmptySession() {
        return (getNewSession());
    }

    /**
     * Get new session class to be used in the doLoad() method.
     */
    protected InternalSession getNewSession() {
        return new StandardSession(this);
    }

    @Override
    public void add(InternalSession session) {
        // 当第一次有session创建的时候，开启session清理线程
        if (!this.backgroundProcessStarted.getAndSet(true)) {
            Thread t = new Thread(() -> {
                while (true) {
                    try {
                        // 每秒清理一次
                        Thread.sleep(StandardSessionManager.this.backgroundProcessorDelay * 1000L);
                        backgroundProcess();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        StandardSessionManager.this.log.error("SessionManagerImpl.backgroundProcess error", e);
                    }
                }
            });
            t.setDaemon(true);
            t.start();
        }

        this.sessions.put(session.getIdInternal(), session);
        int size = getActiveSessions();
        if (size > this.maxActive) {
            synchronized (this.maxActiveUpdateLock) {
                if (size > this.maxActive) {
                    this.maxActive = size;
                }
            }
        }

    }

    /**
     * Return the set of active Sessions associated with this Manager.
     * If this Manager has no active Sessions, a zero-length array is returned.
     */
    @Override
    public InternalSession[] findSessions() {

        return this.sessions.values().toArray(new InternalSession[0]);

    }

    @Override
    public void backgroundProcess() {
        this.count = (this.count + 1) % this.processExpiresFrequency;
        if (this.count == 0) {
            processExpires();
        }
    }

    /**
     * Invalidate all sessions that have expired.
     */
    public void processExpires() {

        long timeNow = System.currentTimeMillis();
        InternalSession sessions[] = findSessions();
        int expireHere = 0;

        if (this.log.isDebugEnabled()) {
            this.log.debug("Start expire sessions {} at {} sessioncount {}", getName(), timeNow, sessions.length);
        }
        for (InternalSession session : sessions) {
            if (session != null && !session.isValid()) {
                expireHere++;
            }
        }
        long timeEnd = System.currentTimeMillis();
        if (this.log.isDebugEnabled()) {
            this.log.debug("End expire sessions {} processingTime {} expired sessions: {}", getName(), timeEnd - timeNow, expireHere);
        }
        this.processingTime += timeEnd - timeNow;

    }


    @Override
    public void setMaxInactiveInterval(int interval) {

        this.maxInactiveInterval = interval;

    }

    /**
     * Set the manager checks frequency.
     *
     * @param processExpiresFrequency the new manager checks frequency
     */
    @Override
    public void setProcessExpiresFrequency(int processExpiresFrequency) {

        if (processExpiresFrequency <= 0) {
            return;
        }

        this.processExpiresFrequency = processExpiresFrequency;

    }

    @Override
    public void setBackgroundProcessorDelay(int backgroundProcessorDelay) {
        this.backgroundProcessorDelay = backgroundProcessorDelay;
    }

    /**
     * Return the descriptive short name of this Manager implementation.
     */
    public String getName() {

        return name;

    }

    /**
     * Set the maximum number of active Sessions allowed, or -1 for
     * no limit.
     *
     * @param max The new maximum number of sessions
     */
    @Override
    public void setMaxActiveSessions(int max) {

        this.maxActiveSessions = max;

    }

}

